home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2010 April
/
PCWorld0410.iso
/
pluginy Firefox
/
2410
/
2410.xpi
/
chrome
/
content
/
foxmarks-network.js
< prev
next >
Wrap
Text File
|
2010-01-28
|
15KB
|
376 lines
/*
Copyright 2007-2009 Xmarks Inc.
foxmarks-network.js: implements network interface to Syncd2.
*/
const NS_ERROR_REDIRECT_LOOP = 2152398879;
const NS_ERROR_CWD_ERROR = 0x804b0016;
/*
The Request class handles communication with a server. It is essentially
an HTTP Javascript remote object broker, delivering objects from the client
to the server and retrieving Javascript objects from the server in return.
Request has built-in support for the Foxmarks authentication protocol,
initiated by a 302 Redirect response.
Arguments:
method: a string indicating which HTTP method to use.
url: either a string or an object containing any of
protocol, host, and path. If the caller provides a string, that
literal string is used as the target url. If an object is
supplied, Request uses whatever components are provided to construct
a final url, employing defaults for missing components.
obj: the object to be transmitted to the server.
opt: an object that contains optional information as described below
Return value:
Three different types of errors can occur in processing a request.
1) Network error: DNS failure, connection reset, etc.
2) Transport failure: HTTP 404, etc.
3) App failure: syncd rejects a request because of revision mismatch.
If an error occurs, we return an integer status code in response.status.
NEW opt parameter is a object/dict with optional values
isAuthRequest - whether this is an authenticate request
headers - any http headers that should be passed in
ignoreBody - don't do any conversion of the body
ignoreAuthTokens - don't update or check for authtokens
noAuthDialog - authenticate but only if it can be done w/o UI
noJSON - the data being returned isn't json so don't parse it
*/
function Request(method, url, bodyobj, opt) {
opt = opt || {};
this._channel = null;
this._streamLoader = null;
this._callback = null;
this._triedAuth = false;
this._headers = opt.headers;
this._ignoreBody = opt.ignoreBody;
this._ignoreAuthTokens = opt.ignoreAuthTokens;
this._noJSON = opt.noJSON;
this._noAuthDialog = opt.noAuthDialog;
try {
this.JSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
} catch(e){
this.JSON = null;
}
// Argument url is either a string or an object
// consisting of protocol, host, and path.
// If it's a string, just take the string that's been
// given. If it's an object, assemble the components
// (applying defaults as appropriate) into a url.
if (url instanceof Object) {
// Set up defaults.
if (!url.protocol) {
if (Xmarks.gSettings.securityLevel == 1 ||
(Xmarks.gSettings.securityLevel == 0 && opt.isAuthRequest)) {
url.protocol = "https";
} else {
url.protocol = "http";
}
}
if (!url.host) url.host = Xmarks.gSettings.host;
if (!url.path) url.path = "/";
if (url.path[0] != "/") url.path = "/" + url.path;
this._url = url.protocol + "://" + url.host + url.path;
} else {
this._url = url;
}
this._bodyobj = bodyobj;
this._method = method;
this._isAuthRequest = opt.isAuthRequest;
}
Request.prototype = {
/*
Process a request. Given the protocol, host, and path, construct a
url and execute the specified method against that url, inserting
the given message body.
When request completes, it calls the provided callback function,
passing a single object (the "response object").
*/
Start: function(callback) {
Xmarks.LogWrite(">>> " + this._method + " " + this._StripPassword(this._url));
if (this._bodyobj && !this._isAuthRequest) {
Xmarks.LogWrite(">>> Body is: " +
(!Xmarks.gSettings.getDebugOption("no-verbose") ?
this._bodyobj.toJSONString() : "(disabled)"));
}
this._callback = callback;
// Create a channel.
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
try {
this._channel = ios.newChannelFromURI(ios.newURI(this._url,
"UTF-8", null));
} catch (e) {
Xmarks.LogWrite("Couldn't create channel from " + this._url + ", error:" + e.toSource());
callback(1008);
return;
}
this._channel.QueryInterface(Ci.nsIUploadChannel);
this._channel.loadFlags |=
Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
// If we have a body to transmit, set it up as an upload stream.
if (this._bodyobj) {
var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
var stream = converter.convertToInputStream(
this._bodyobj.toJSONString());
this._channel.setUploadStream(stream, "application/json", -1);
}
// Special setup only for HTTP channels.
if (this._channel instanceof Ci.nsIHttpChannel) {
// Set the channel's method if necessary.
if (this._method) {
this._channel.requestMethod = this._method;
}
// Disable redirection.
this._channel.redirectionLimit = 0;
this._channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
// Set the user agent.
this._channel.setRequestHeader("User-Agent",
this._channel.getRequestHeader("User-Agent") +
" Xmarks-Fx/" + Xmarks.FoxmarksVersion(), false);
if(!this._ignoreAuthTokens){
var auth = Xmarks.gSettings.auth || "";
if(auth.length > 0){
this._channel.setRequestHeader("Authorization",
"XMAuth " + auth, false);
this._channel.setRequestHeader("X-Xmarks-Auth",
auth, false);
}
}
// Set other headers if provided.
if (this._headers) {
var self = this;
forEach(this._headers, function(v, k) {
self._channel.setRequestHeader(k, v, false);
} );
}
}
// Create a stream loader for retrieving the response.
this._streamLoader = Cc["@mozilla.org/network/stream-loader;1"]
.createInstance(Ci.nsIStreamLoader);
// Fire it up. Note that this results in the channel's AsyncOpen
// being called; if we have set an upload stream above, it will be
// transmitted as part of the request. This is a bit strange, but
// appears to be correct.
try {
// Before Firefox 3...
this._streamLoader.init(this._channel, this, null);
} catch(e) {
// Firefox 3 style...
this._streamLoader.init(this);
this._channel.asyncOpen(this._streamLoader, null);
}
return;
},
Cancel: function() {
if (this._channel && this._channel.isPending()){
Xmarks.LogWrite("Cancelling Network Request");
this._channel.cancel(0x804b0002);
}
},
onStreamComplete: function(loader, ctxt, status, resultLength, result) {
var response = {};
if (Components.isSuccessCode(status)) {
try {
status = this._channel.responseStatus || 200;
} catch (e) {
this._callback( { status: 1005, errormsg:
"Disable automatic proxy settings detection."} );
return;
}
//try {
// dump("cookie: " + this._channel.getResponseHeader("Set-Cookie") + "\n");
// }catch(e){}
var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "utf-8";
var msg = "";
if (status == 200 || status == 201 || status == 204) {
if(!this._ignoreBody){
msg = converter.convertFromByteArray(result, resultLength);
}
if (msg && msg.length && !this._ignoreBody) {
if(this._noJSON){
response = {
status: 0,
txt: msg
};
} else {
try {
response = this.JSON ? this.JSON.decode(msg) :
eval("(" + msg + ")");
if(response.auth && response.auth.length > 0){
Xmarks.gSettings.auth = response.auth;
}
if(response.username && response.username.toLowerCase() != Xmarks.gSettings.username.toLowerCase() && Xmarks.gSettings.username.indexOf('@') < 0 ){
response = { status: 1010,
errormsg: "Invalid server response" };
Xmarks.LogWrite("CurrUser: " + Xmarks.gSettings.username);
Xmarks.LogWrite("ServerUser: " + response.username);
Xmarks.LogWrite("JSON Response: " + msg);
}
} catch(e) {
Xmarks.LogWrite("Failed to parse message: " + msg);
response = { status: 1010,
errormsg: "Invalid server response" };
}
}
}
if (response.status == null) {
response.status = 0;
}
// New Auth stuff
if(response.status == 302){
function RestartAfterAuth(response) {
if (response.status == 0){
Xmarks.gSettings.auth = response.auth;
// We're authenticated. Retry original request.
self.Start(self._callback);
return;
} else {
// Auth failed. Return failure code.
if (response.message == "Wrong username or password.") {
response.status = 401;
}
self._callback(response);
return;
}
}
Xmarks.LogWrite(">>> Authenticating...");
if (this._triedAuth) {
Xmarks.LogWrite("In authentication loop. Possible proxy server caching issue.");
this._callback( { status: 1012 } );
return;
}
if (this._noAuthDialog) {
if ((Xmarks.gSettings.rememberPassword &&
Xmarks.gSettings.masterPasswordSet) ||
(!Xmarks.gSettings.rememberPassword &&
!Xmarks.gSettings.sessionPassword)) {
Xmarks.LogWrite("Client skips authentication");
this._callback( { status: 403 } );
return;
}
}
var location = response.authtoken_location;
var self = this; // keep a handle on the original request
// parse out the protocol, host, and path
var colonslashslash = location.indexOf("://");
var nextslash = location.indexOf("/", colonslashslash + 3);
if (colonslashslash < 0 || nextslash < 0) {
throw Error("Couldn't parse location string " + location);
}
var url = {}
url.host = location.substr(colonslashslash + 3,
nextslash - (colonslashslash + 3));
url.host = Xmarks.gSettings.getCharPref('host-login', url.host);
url.path = location.substr(nextslash);
Xmarks.LogWrite("Requesting password.");
try {
var pw = Xmarks.gSettings.password;
} catch (e) {
Xmarks.LogWrite("User canceled password request.");
this._callback(2);
return;
}
var authReq = new Request(
"POST",
url,
{
username: Xmarks.gSettings.username,
password: Base64.encode(pw)
},
{
isAuthRequest: true
}
);
this._triedAuth = true;
authReq.Start(RestartAfterAuth);
return;
}
} else {
response = { status: status, "errormsg" : msg };
}
try { // Pass back the etag if there is one.
response.etag = this._channel.getResponseHeader("Etag");
} catch (e) {}
if(Xmarks.gSettings.getDebugOption("no-verbose")){
Xmarks.LogWrite(">>> Callback (disabled)");
} else {
var oldauth = response.auth;
delete response.auth;
if(this._noJSON){
Xmarks.LogWrite(">>> Callback (not json, status: " + response.status + ")");
} else {
Xmarks.LogWrite(">>> Callback " + response.toSource());
}
response.auth = oldauth;
}
this._callback(response);
return;
} else {
if (status == NS_ERROR_REDIRECT_LOOP && !this._isAuthRequest) {
this._callback( { status: status });
return;
} else if (status == NS_ERROR_CWD_ERROR) {
Xmarks.LogWrite("CWD error (mapping to 404)");
this._callback( { status: 404 } );
} else {
Xmarks.LogWrite("network request failed; status is " +
status.toString(16));
this._callback( { status: status });
}
}
},
_StripPassword: function(url) {
var exp = /^(.*):(.*)@(.*)$/
var match = url.match(exp);
return match ? match[1] + "@" + match[3] : url;
}
}